C/C++의 보안 취약점

C/C++의 보안 취약점

1. 서론: C/C++ 보안, 패러다임의 전환점에 서다

1.1 현황 진단: 경고등이 켜진 C/C++ 생태계

C와 C++ 프로그래밍 언어는 수십 년간 소프트웨어 개발의 근간을 이루어 왔으나, 오늘날 그 보안성에 대한 근본적인 회의론이 최고조에 달하고 있다. 최근 미국 백악관 국가사이버국장실(ONCD), 국가안보국(NSA), 사이버보안 및 인프라 보안국(CISA) 등 주요 정부 기관들은 연이어 보고서를 발표하며 C/C++와 같은 메모리 비안전(memory-unsafe) 언어의 사용을 지양하고 메모리 안전(memory-safe) 언어로의 전환을 강력히 촉구하고 있다.1 이는 C/C++의 보안 문제가 더 이상 개별 소프트웨어의 결함 수준을 넘어, 국가 핵심 인프라와 안보를 위협하는 체계적 위험으로 인식되고 있음을 명백히 보여준다.2

이러한 움직임은 단순한 권고를 넘어 구체적인 통계와 역사적 사실에 기반한다. Microsoft와 Google과 같은 세계적인 기술 기업들의 최근 연구에 따르면, 전체 소프트웨어 보안 취약점의 약 70%가 메모리 안전 문제, 즉 메모리를 다루는 과정에서 발생하는 오류에서 기인하는 것으로 분석되었다.1 이 놀라운 수치는 C/C++가 가진 태생적 한계가 얼마나 광범위한 보안 위협을 양산하고 있는지를 정량적으로 증명한다.

역사적으로도 C/C++의 메모리 취약점은 심각한 사이버 재난의 진원지였다. 1988년 인터넷을 마비시켰던 모리스 웜(Morris Worm)부터 2014년 전 세계 웹 서버의 암호화 키를 유출시킨 하트블리드(Heartbleed) 취약점, 그리고 2023년의 BLASTPASS 익스플로잇 체인에 이르기까지, 역사에 기록된 굵직한 사이버 공격들은 대부분 C/C++의 메모리 관리 부실을 파고들었다.2 이는 해당 문제가 수십 년간 해결되지 못한 채 반복되고 있는 고질적인 병폐임을 시사한다.

이처럼 정부 기관과 산업계 전반에서 C/C++의 위험성을 경고하는 목소리가 높아지는 현상은 중요한 패러다임의 전환을 의미한다. 과거에는 이미 발생한 취약점을 찾아내고 패치를 적용하는 사후 대응(reactive) 방식이 주를 이루었다. 그러나 이제는 취약점의 근본 원인이 되는 프로그래밍 언어 자체의 특성을 문제 삼으며, 설계 단계에서부터 안전을 보장하는 사전 예방(proactive) 방식으로의 전환을 요구하고 있다. 이는 언어 선택이 단순한 개발 효율성이나 성능의 문제를 넘어, 제품과 서비스, 나아가 사회 전체의 안전과 직결되는 핵심적인 보안 전략의 일부가 되었음을 의미한다. 국가 안보 기관들이 이 문제에 직접 개입하는 것은, C/C++의 메모리 안전성 결함이 상업적 손실을 넘어 국가적 차원의 시스템 리스크로 격상되었음을 방증하는 것이다.

1.2 보고서의 목적과 범위

본 보고서는 “C, C++이 보안 이슈가 많은가?“라는 질문에 대해 표면적인 답변을 넘어, 심층적이고 다각적인 분석을 제공하는 것을 목표로 한다. 이를 위해 C/C++가 지닌 설계 철학의 본질과 그로 인해 파생되는 태생적 위험성을 먼저 규명할 것이다.

그 후, 버퍼 오버플로우(Buffer Overflow), 해제 후 사용(Use-After-Free) 등 C/C++에서 가장 빈번하게 발생하는 대표적인 보안 취약점들의 작동 원리를 기술적으로 상세히 해부하고, 실제 공격으로 이어지는 메커니즘을 코드 예제와 함께 분석한다.

나아가, 이러한 문제에 대한 대안으로 부상하고 있는 메모리 안전 언어들, 특히 가비지 컬렉션(Garbage Collection) 기반 언어와 Rust의 소유권(Ownership) 모델이 어떻게 근본적으로 다른 접근 방식을 통해 안전성을 확보하는지 비교 분석할 것이다.

마지막으로, 거대한 C/C++ 레거시 생태계가 존재하는 현실을 직시하고, 현대적 C++ 기능, 정적/동적 분석 도구, 보안 코딩 표준 등을 활용하여 기존 코드의 보안을 강화할 수 있는 현실적인 완화 전략을 종합적으로 제시한다. 본 보고서는 단순히 C/C++의 위험성을 나열하는 데 그치지 않고, ‘왜 위험한지’, ‘어떻게 위험한지’, 그리고 ’어떻게 대응해야 하는지’에 대한 깊이 있는 통찰을 제공함으로써 개발자와 보안 전문가, 기술 정책 결정권자들에게 실질적인 가이드를 제공하고자 한다.

2. C/C++의 설계 철학과 태생적 위험: 성능을 위한 트레이드오프

C/C++에 내재된 수많은 보안 취약점은 우연한 결함이 아니라, ’최고의 성능’이라는 목표를 달성하기 위해 ’안전성’을 의도적으로 희생한 설계 철학의 필연적 결과물이다. 수십 년 전 컴퓨팅 자원이 극도로 제한적이던 시절에 확립된 이 철학은 오늘날 막대한 ’보안 부채(Security Debt)’가 되어 소프트웨어 산업 전체에 큰 부담을 지우고 있다.

2.1 저수준 제어와 하드웨어 직접 접근

C 언어는 본래 운영체제(OS)와 같은 시스템 소프트웨어를 개발하기 위해 탄생했다. 따라서 하드웨어의 모든 자원을 남김없이 활용하고 제어할 수 있는 능력이 필수적이었다. 이를 위해 C는 프로그래머에게 메모리 주소를 직접 가리키고 그 내용을 조작할 수 있는 ’포인터(pointer)’라는 강력하면서도 위험한 도구를 제공했다.7 C++ 역시 C의 이러한 특성을 계승하여, 임베디드 시스템, 하드웨어 드라이버, 고성능 게임 엔진 등 하드웨어와 밀접하게 상호작용하며 극한의 성능을 요구하는 분야에서 독보적인 위치를 차지하게 되었다.2

이러한 저수준 제어 능력은 프로그래머에게 하드웨어를 완벽하게 통제할 수 있는 자유를 주었지만, 동시에 모든 안전장치를 스스로 마련해야 하는 막중한 책임을 부여하는 ’양날의 검’이 되었다.2 포인터를 잘못 사용하면 의도치 않은 메모리 영역을 침범하여 데이터를 손상시키거나 시스템 전체를 불안정하게 만들 수 있으며, 이는 곧 보안 취약점의 직접적인 원인이 된다.7

2.2 수동 메모리 관리: 모든 책임은 프로그래머에게

C/C++의 핵심 철학 중 하나는 ’런타임 오버헤드의 최소화’이다. 이러한 철학은 메모리 관리 방식에서 극명하게 드러난다. Java, Python, C#과 같은 현대 프로그래밍 언어들은 ’가비지 컬렉터(Garbage Collector)’라는 자동 메모리 관리 시스템을 내장하고 있다. 가비지 컬렉터는 프로그램 실행 중에 더 이상 사용되지 않는 메모리를 주기적으로 찾아내 자동으로 해제해주므로, 개발자가 메모리 관리에 대한 부담을 덜 수 있다.10

반면, C/C++는 이러한 자동화된 장치를 제공하지 않는다. 프로그래머는 malloc()이나 new 키워드를 사용해 필요한 메모리를 직접 운영체제로부터 할당받고, 사용이 끝난 후에는 free()delete를 호출하여 명시적으로 반납해야 한다.7 이를 ’수동 메모리 관리(Manual Memory Management)’라고 한다. 이 방식은 가비지 컬렉터가 유발하는 예측 불가능한 성능 저하 없이, 메모리 할당과 해제 시점을 완벽하게 통제하여 최고의 성능을 보장할 수 있다는 장점이 있다.8

그러나 이 장점은 프로그래머가 단 한 번의 실수도 없이 완벽하게 메모리를 관리해야 한다는 전제를 깔고 있다. 복잡한 프로그램에서는 수많은 메모리 할당이 일어나며, 이 모든 할당에 대해 정확한 시점에 해제가 이루어지는지 추적하는 것은 매우 어렵고 오류가 발생하기 쉽다. 만약 프로그래머가 메모리 해제를 잊어버리면 ’메모리 누수’가 발생하고, 이미 해제된 메모리에 다시 접근하면 ’해제 후 사용(Use-After-Free)’과 같은 치명적인 버그가 발생한다. 이처럼 수동 메모리 관리는 인간의 실수가 필연적으로 개입될 수밖에 없는 구조이기에, 잠재적인 보안 취약점의 가장 큰 온상이 되고 있다.2

2.3 ’프로그래머를 신뢰한다’는 위험한 철학

C/C++의 근간에는 “프로그래머는 자신이 무엇을 하는지 정확히 알고 있다(The programmer knows what they are doing)“는 강력한 신뢰가 깔려 있다. 이 철학에 따라, 컴파일러는 프로그래머의 의도를 최대한 존중하며, 불필요하다고 판단되는 안전 검사를 수행하지 않음으로써 코드의 실행 속도를 높이는 데 집중한다.

가장 대표적인 예가 ’경계 검사(bounds checking)’의 부재이다. C-스타일 배열에 데이터를 쓸 때, 할당된 크기를 초과하는지 여부를 컴파일러나 런타임 환경이 자동으로 확인해주지 않는다.14 예를 들어, 10바이트 크기로 선언된 버퍼에 12바이트의 데이터를 쓰라는 코드가 있다면, C/C++는 이를 막지 않고 그대로 실행하여 버퍼 뒤에 위치한 다른 메모리 영역을 침범하게 된다.9 이는 버그가 아니라, 성능을 위해 안전장치를 제거한 의도된 설계의 결과이다.

이러한 설계 철학은 전문가에게는 최고의 유연성과 성능을 제공하지만, 사소한 실수 하나가 곧바로 심각한 메모리 손상 취약점으로 이어지는 결과를 낳았다. 수십 년 전, CPU 사이클 하나와 메모리 1바이트가 귀했던 시절에는 합리적인 선택이었을지 모르나, 오늘날 이 선택은 막대한 ’보안 부채’로 남았다. 현재 업계가 정적/동적 분석 도구 개발, 보안 코딩 표준 제정, 취약점 패치, 사이버 공격 대응 등에 쏟아붓는 천문학적인 비용은, 바로 이 ‘성능 우선’ 철학이 남긴 대가를 치르는 과정이라고 할 수 있다. 최근 메모리 안전 언어로의 전환을 촉구하는 움직임은, 이 부채를 더 이상 감당할 수 없다는 산업계의 선언이자, 안전을 우선하는 새로운 설계 패러다임으로의 전환을 요구하는 목소리인 것이다.

3. C/C++의 대표적 보안 취약점 심층 분석

C/C++의 설계 철학은 다양한 형태의 메모리 관련 보안 취약점을 낳았다. 이들 취약점은 단순히 프로그램의 오작동을 넘어, 시스템의 제어권을 탈취당하는 심각한 보안 사고로 이어질 수 있다. 본 장에서는 가장 대표적이고 파급력이 큰 취약점들의 기술적 원리와 공격 메커니즘을 심층적으로 분석한다.

취약점 유형근본 원인 (언어적 특성)주요 영향개념적 코드 패턴
버퍼 오버플로우경계 검사(Bounds Checking) 부재, C-스타일 배열 및 불안전한 문자열 함수(strcpy, gets 등)임의 코드 실행(RCE), 서비스 거부(DoS), 데이터 변조char buffer; strcpy(buffer, user_input);
해제 후 사용 (UAF)수동 메모리 관리, 해제된 메모리를 가리키는 허상 포인터(Dangling Pointer)임의 코드 실행(RCE), 데이터 손상, 프로그램 충돌char* ptr = new char; delete ptr; ptr = 'A';
포맷 스트링 버그printf 계열 함수에서 포맷 스트링과 인자의 분리 원칙 위반메모리 내용 유출, 임의 메모리 쓰기, 임의 코드 실행char buffer;...; printf(buffer);
메모리 누수수동 메모리 관리, 할당된 메모리의 미해제(free/delete 누락)서비스 거부(DoS), 시스템 성능 저하while(true) { new char; }
이중 해제수동 메모리 관리, 동일 메모리 주소에 대한 중복 free/delete 호출힙 손상, 예측 불가능한 동작, 임의 코드 실행char* ptr = new char; delete ptr; delete ptr;
널 포인터 역참조포인터가 NULL/nullptr인 상태에서 메모리 접근 시도프로그램 충돌 (주로 세그멘테이션 오류)char* ptr = nullptr; *ptr = 'A';

3.1 버퍼 오버플로우 (Buffer Overflow)

버퍼 오버플로우는 C/C++ 보안의 역사에서 가장 상징적이고 파괴적인 취약점이다. 이는 프로그램이 데이터를 버퍼(메모리의 임시 저장 공간)에 저장할 때, 할당된 공간의 경계를 넘어 인접한 메모리 영역까지 침범하는 현상을 말한다.14

3.1.1 개념 및 원인

버퍼 오버플로우의 근본 원인은 C/C++ 언어 자체의 특성에 있다. C-스타일 배열(char buffer;)은 자신의 크기 정보를 내부에 가지고 있지 않다. 따라서 배열에 데이터를 복사할 때, 프로그래머가 직접 데이터의 길이가 배열의 크기를 넘지 않는지 확인해야 한다. 그러나 strcpy(), strcat(), gets(), scanf()와 같은 C 표준 라이브러리의 일부 함수들은 이러한 경계 검사를 자동으로 수행하지 않는다.14 예를 들어, strcpy(destination, source) 함수는 source 문자열의 끝을 알리는 널 문자(\0)를 만날 때까지 무조건 destination 버퍼로 복사를 계속한다. 만약 source의 길이가 destination 버퍼의 크기보다 크다면, 버퍼 오버플로우가 발생하여 destination 버퍼 뒤에 위치한 메모리를 훼손하게 된다.4

3.1.2 공격 메커니즘: 스택 오버플로우와 제어권 탈취

버퍼 오버플로우 공격 중 가장 고전적인 형태는 ’스택 기반 버퍼 오버플로우(Stack-based Buffer Overflow)’이다. 함수의 지역 변수는 스택(Stack)이라는 메모리 영역에 저장되는데, 스택에는 지역 변수 외에도 함수가 종료된 후 돌아갈 위치를 저장하는 ’반환 주소(Return Address)’가 함께 저장된다. 공격의 핵심 원리는 다음과 같다.14

  1. 공격 대상 선정: 함수 내에 외부 입력(예: 사용자 이름, 파일 경로)을 검증 없이 고정 크기 버퍼에 복사하는 코드가 공격 대상이 된다.
  2. 악성 페이로드 구성: 공격자는 버퍼의 크기를 초과하는 매우 긴 입력값을 만든다. 이 입력값(페이로드)은 일반적으로 + +로 구성된다.
  3. 메모리 덮어쓰기: 프로그램이 이 악성 입력값을 버퍼에 복사하면, 버퍼 오버플로우가 발생한다. 긴 입력값은 버퍼를 가득 채우고, 스택에 저장된 다른 지역 변수, 이전 함수의 프레임 포인터(EBP), 그리고 마침내 반환 주소까지 덮어쓰게 된다.17
  4. 실행 흐름 조작: 공격자는 원래의 반환 주소가 있던 위치를 자신이 페이로드에 포함시킨 셸코드의 메모리 주소로 덮어쓴다.
  5. 제어권 탈취: 함수가 정상적으로 종료되면, CPU는 스택에 저장된 (이제는 조작된) 반환 주소로 점프한다. 그 결과, 프로그램은 원래의 실행 흐름으로 돌아가는 대신 공격자가 심어놓은 셸코드를 실행하게 되며, 공격자는 시스템의 제어권을 탈취하게 된다.17

3.1.3 방어 기법

이러한 공격에 대응하기 위해 현대 운영체제와 컴파일러는 여러 방어 기법을 도입했다.

  • 스택 카나리 (Stack Canaries): 함수 시작 시 스택의 반환 주소 앞에 ’카나리’라는 무작위 값을 삽입한다. 함수 종료 전에 이 카나리 값이 변경되었는지 확인하여, 변경되었다면 버퍼 오버플로우가 발생한 것으로 간주하고 프로그램을 즉시 종료시킨다.14
  • 주소 공간 배치 난수화 (Address Space Layout Randomization, ASLR): 프로그램이 실행될 때마다 스택, 힙, 라이브러리 등의 메모리 주소를 무작위로 변경한다. 이로 인해 공격자는 셸코드나 악용할 함수의 정확한 주소를 예측하기 어려워진다.14
  • 데이터 실행 방지 (Data Execution Prevention, DEP): 스택이나 힙과 같은 데이터 저장 영역에 실행 권한을 부여하지 않는다. 공격자가 셸코드를 메모리에 삽입하더라도, 해당 메모리 영역에서 코드를 실행하려는 시도를 CPU 수준에서 차단한다.14

3.2 해제 후 사용 (Use-After-Free, UAF)

UAF는 수동 메모리 관리의 복잡성에서 비롯되는 또 다른 치명적인 취약점이다. 이는 free()delete를 통해 메모리 할당이 해제된 이후에도, 해당 메모리 위치를 가리키는 포인터를 계속해서 사용하는 경우에 발생한다.15

3.2.1 개념 및 원인

C/C++에서 메모리를 해제하면 해당 메모리 공간은 운영체제에 반납되어 다른 용도로 재사용될 수 있게 된다. 하지만 메모리를 가리키던 포인터 변수 자체는 사라지지 않고, 여전히 예전 메모리 주소를 값으로 가지고 있게 된다. 이러한 포인터를 ‘허상 포인터’ 또는 ’댕글링 포인터(Dangling Pointer)’라고 부른다.8 프로그래머가 실수로 이 댕글링 포인터를 통해 메모리에 접근(읽기 또는 쓰기)하면 UAF 취약점이 발생한다.23

UAF는 주로 다음과 같은 상황에서 발생한다.

  • 복잡한 로직: 프로그램의 로직이 복잡하여 메모리 해제 책임이 누구에게 있는지 불분명할 때.23
  • 예외 처리 오류: 오류나 예외가 발생하는 특정 경로에서만 메모리가 해제되고, 다른 경로에서는 이를 인지하지 못한 채 댕글링 포인터를 계속 사용할 때.23
  • 부주의한 코딩: 메모리 해제 후 해당 포인터를 nullptr로 명시적으로 초기화하지 않아 댕글링 포인터가 남게 될 때.8

3.2.2 공격 메커니즘: 힙 스프레이와 제어권 탈취

UAF 취약점은 공격자에게 매우 강력한 공격 수단을 제공한다. 공격 메커니즘은 보통 다음과 같은 단계를 거친다.24

  1. 객체 할당 및 해제 유발: 공격자는 먼저 특정 구조나 클래스(예: 가상 함수를 포함하는 C++ 객체)의 인스턴스를 힙 메모리에 할당하도록 프로그램을 유도한다. 그 후, 프로그램의 특정 기능을 이용해 해당 객체의 메모리를 해제시킨다. 이 때, 객체를 가리키던 포인터는 댕글링 포인터가 된다.
  2. 힙 스프레이 (Heap Spraying): 공격자는 해제된 메모리 공간이 다른 데이터로 채워지기 전에, 해당 공간과 동일한 크기의 악의적으로 조작된 데이터를 대량으로 할당하도록 프로그램을 유도한다. 이 과정을 ’힙 스프레이’라고 한다. 운영체제의 메모리 할당자는 이전에 해제된 공간에 공격자의 데이터를 재할당할 가능성이 높다.
  3. 가상 함수 테이블(vtable) 조작: 만약 원래 객체가 가상 함수를 가지고 있었다면, 객체의 메모리 맨 앞부분에는 가상 함수들의 주소를 담고 있는 테이블(vtable)을 가리키는 포인터가 존재한다. 공격자는 힙 스프레이를 통해 이 vtable 포인터가 위치했던 자리를 자신이 제어하는 가짜 vtable의 주소로 덮어쓴다. 이 가짜 vtable은 셸코드의 주소를 가리키도록 조작되어 있다.
  4. 제어권 탈취: 프로그램이 나중에 댕글링 포인터를 통해 원래 객체의 가상 함수를 호출하면, 실제로는 조작된 vtable 포인터를 따라가 가짜 vtable에 등록된 셸코드를 실행하게 된다. 이로써 공격자는 임의 코드 실행(RCE)에 성공한다.23

예를 들어, 다음 C++ 코드는 UAF 취약점을 보여준다.21

C++

#include <iostream>

class VulnerableObject {
public:
virtual void vulnerableMethod() {
std::cout << "Method called." << std::endl;
}
};

int main() {
VulnerableObject* obj = new VulnerableObject();
//... 프로그램 로직 수행...
delete obj; // 메모리 해제. 이제 'obj'는 댕글링 포인터가 됨.
//... 다른 메모리 할당이 일어날 수 있음 (힙 스프레이 기회)...
obj->vulnerableMethod(); // UAF 발생! 해제된 메모리에 접근하여 가상 함수 호출 시도.
return 0;
}

이 코드에서 delete obj; 이후 obj->vulnerableMethod();를 호출하는 시점에, obj가 가리키던 메모리 공간이 공격자의 데이터로 채워졌다면, 조작된 vtable을 통해 악성 코드가 실행될 수 있다.

3.3 포맷 스트링 버그 (Format String Bug, FSB)

포맷 스트링 버그는 printf와 같은 C의 입출력 함수를 부주의하게 사용할 때 발생하는 심각한 취약점이다. 이는 프로그래머의 사소한 실수가 메모리 내용 유출 및 임의 코드 실행으로까지 이어질 수 있음을 보여주는 대표적인 사례이다.27

3.3.1 개념 및 원인

C언어의 printf 함수는 첫 번째 인자로 ’포맷 스트링’을 받는다. 포맷 스트링은 출력할 문자열의 형식을 지정하며, %d(정수), %s(문자열), %x(16진수)와 같은 ’형식 지정자(format specifier)’를 포함할 수 있다. printf는 형식 지정자를 만나면, 그에 해당하는 추가 인자들을 스택에서 순서대로 가져와 출력한다.

올바른 사용법은 다음과 같다.

C

char name = "Alice";
int age = 30;
printf("Name: %s, Age: %d\n", name, age);

그러나 프로그래머가 외부로부터 입력받은 문자열을 포맷 스트링으로 직접 사용하면 문제가 발생한다.

C

char user_input;
scanf("%s", user_input);
printf(user_input); // 취약한 코드!

만약 사용자가 user_input"%x %x %x"와 같은 문자열을 입력하면, printf 함수는 이를 포맷 스트링으로 인식한다. 하지만 형식 지정자 %x에 대응하는 추가 인자들이 없으므로, 함수는 스택의 현재 위치부터 차례대로 값을 읽어와 16진수로 출력하게 된다. 이 과정에서 스택에 저장된 함수의 반환 주소, 지역 변수 등 민감한 정보가 유출될 수 있다.28

3.3.2 공격 메커니즘: 메모리 읽기와 쓰기

공격자는 포맷 스트링의 특정 지정자들을 악용하여 단순히 메모리를 읽는 것을 넘어, 원하는 메모리 주소에 원하는 값을 쓸 수도 있다.28

  • 메모리 읽기 (%x, %p, %s):
  • %x 또는 %p: 스택에 있는 값들을 16진수 또는 포인터 주소 형태로 출력하여 메모리 레이아웃, 카나리 값, 반환 주소 등을 알아낼 수 있다.28
  • %s: 스택에 있는 값을 주소로 해석하여 해당 주소의 문자열을 출력한다. 이를 이용해 임의 주소의 메모리 내용을 읽을 수 있다.
  • 메모리 쓰기 (%n):
  • %n 지정자는 다른 지정자들과 달리 값을 출력하지 않는다. 대신, %n이 나오기 전까지 출력된 문자의 총 개수를, 그에 해당하는 인자로 주어진 포인터가 가리키는 메모리 주소에 정수 값으로 기록한다.28
  • 예를 들어, printf("abc%n", &count);는 “abc“를 출력하고, 변수 count에 3을 저장한다.
  • 공격 결합 (%<N>$%n):
  • 공격자는 먼저 %p를 여러 번 사용하여 자신이 입력한 특정 문자열이 스택의 몇 번째 인자에 위치하는지(N) 알아낸다.
  • 그 후, 공격 페이로드를 [쓰고 싶은 주소] + + %<N>$n 형태로 구성한다.
  • printf는 이 페이로드를 처리하면서, %<N>$n을 만난다.
  • $<N> 파라미터는 printf에게 N번째 인자를 참조하라고 지시한다. N번째 인자 위치에는 공격자가 미리 넣어둔 ’쓰고 싶은 주소’가 있다.
  • %n은 ’쓰고 싶은 주소’와 Dummy Data의 길이만큼의 숫자를 ’쓰고 싶은 주소’가 가리키는 메모리 공간에 기록한다.
  • 공격자는 Dummy Data의 길이를 조절함으로써 원하는 값을 정확히 쓸 수 있다.28

이러한 기법을 통해 공격자는 함수의 반환 주소를 셸코드 주소로 덮어쓰거나, GOT(Global Offset Table) 항목을 조작하여 라이브러리 함수 호출을 가로채는 등 프로그램의 실행 흐름을 완전히 제어할 수 있다.27

3.4 기타 메모리 관련 오류

위에서 설명한 주요 취약점 외에도 C/C++의 수동 메모리 관리는 다양한 형태의 오류를 유발한다.

  • 메모리 누수 (Memory Leak): newmalloc으로 동적 할당된 메모리를 사용 후 deletefree로 해제하지 않을 때 발생한다.12 누수된 메모리는 프로그램이 종료될 때까지 시스템에 반환되지 않는다. 단일 실행 프로그램에서는 큰 문제가 아닐 수 있지만, 웹 서버와 같이 장시간 중단 없이 실행되어야 하는 애플리케이션에서는 누수가 누적되어 결국 가용 메모리를 모두 소진시키고, 이는 시스템 전체의 성능 저하나 서비스 거부(DoS) 공격으로 이어질 수 있다.7
  • 이중 해제 (Double Free): 이미 해제된 메모리 블록을 가리키는 포인터를 사용하여 다시 freedelete를 호출할 때 발생한다.12 이 행위는 힙 메모리 관리자가 사용하는 내부 자료구조(예: free list)를 손상시킬 수 있다. 손상된 힙 상태는 예측 불가능한 프로그램 충돌을 일으키거나, 정교하게 조작될 경우 공격자가 힙 할당 과정을 제어하여 임의 코드 실행으로 이어질 수 있는 심각한 보안 취약점을 야기한다.34
  • 널 포인터 역참조 (Null Pointer Dereference): NULL 또는 C++11 이후의 nullptr는 ’아무것도 가리키지 않음’을 의미하는 특별한 포인터 값이다. 이 널 포인터가 가리키는 주소에 데이터를 읽거나 쓰려고 시도하면 이 오류가 발생한다.8 대부분의 현대 운영체제는 주소 0번지에 대한 접근을 허용하지 않으므로, 이 오류는 일반적으로 세그멘테이션 오류(Segmentation Fault)를 일으키며 프로그램을 즉시 비정상 종료시킨다. 이는 주로 서비스 거부 공격에 사용될 수 있다.22

이러한 다양한 메모리 오류들은 모두 C/C++가 프로그래머에게 메모리에 대한 완전한 통제권을 부여하면서, 그에 상응하는 안전장치를 충분히 제공하지 않기 때문에 발생하는 구조적인 문제이다.

4. 대안으로서의 메모리 안전 언어

C/C++의 고질적인 메모리 안전성 문제는 프로그래밍 언어 설계에 대한 근본적인 성찰을 가져왔다. 그 결과, 개발자의 실수를 원천적으로 방지하고 메모리 안전성을 언어 차원에서 보장하는 ’메모리 안전 언어(Memory-Safe Languages)’가 주목받게 되었다. 이들 언어는 크게 ‘가비지 컬렉션’ 방식과 ‘소유권’ 시스템 방식으로 나눌 수 있으며, 각각 다른 접근법을 통해 C/C++의 위험성을 해결한다.

특성C / C++Java, Python, Go, C#Rust
메모리 관리 방식수동 (Manual)자동 (가비지 컬렉션)자동 (소유권 시스템)
안전성 보장 시점런타임 (프로그래머 책임)런타임 (GC)컴파일 시점 (컴파일러)
성능 오버헤드없음 (이론상 최저)GC 실행 시 발생 (Pause time)없음 (Zero-cost abstraction)
주요 방지 취약점-메모리 누수, UAF, 이중 해제메모리 누수, UAF, 이중 해제, 데이터 경쟁(Data Race) 등
실시간성 보장가능어려움 (GC의 비결정성)가능
개발 복잡성높음 (메모리 관리 부담)낮음중간 (소유권 모델 학습 필요)

4.1 가비지 컬렉션(Garbage Collection) 기반 언어: Java, Python, Go, C#

가비지 컬렉션(GC)은 자동 메모리 관리의 가장 보편적인 형태로, Java, Python, Go, C# 등 다수의 현대 언어에서 채택하고 있다.1

4.1.1 작동 원리

GC의 핵심 아이디어는 간단하다. 프로그램이 실행되는 동안, ’가비지 컬렉터’라는 런타임 구성 요소가 주기적으로 힙 메모리를 스캔하여 더 이상 프로그램의 어떤 부분에서도 참조되지 않는 ‘쓰레기(garbage)’ 객체를 식별한다. 식별된 쓰레기 객체들이 차지하고 있던 메모리는 자동으로 회수되어 시스템에 반환된다.10

가장 기본적인 방식은 ’참조 계수(Reference Counting)’이다. 모든 객체는 자신을 참조하는 변수의 개수를 세는 카운터를 가진다. 새로운 참조가 생기면 카운터가 1 증가하고, 참조가 사라지면 1 감소한다. 카운터가 0이 되면 해당 객체는 아무도 사용하지 않는 것이므로 즉시 메모리에서 해제된다.37 Python이 이 방식을 기본으로 사용한다. 그러나 참조 계수 방식은 두 객체가 서로를 참조하는 ‘순환 참조’ 상황을 해결하지 못하는 단점이 있다.39 이를 보완하기 위해 대부분의 GC는 ’Mark-and-Sweep’과 같은 알고리즘을 함께 사용한다. 이 방식은 프로그램의 루트(전역 변수, 스택 등)에서부터 도달 가능한 모든 객체를 표시(Mark)한 후, 표시되지 않은 모든 객체를 쓰레기로 간주하여 수거(Sweep)한다.

4.1.2 보안상 이점

GC의 가장 큰 보안상 이점은 C/C++의 수동 메모리 관리에서 발생하는 대부분의 치명적인 오류를 원천적으로 방지한다는 것이다.

  • 메모리 누수 방지: 개발자가 메모리 해제를 잊더라도 GC가 알아서 수거해가므로, 장기 실행 애플리케이션에서 발생할 수 있는 점진적인 메모리 고갈 문제를 막을 수 있다.11
  • 해제 후 사용(UAF) 및 이중 해제 방지: 메모리 해제 시점과 방법을 전적으로 런타임이 결정하므로, 개발자가 이미 해제된 메모리에 접근하거나 동일한 메모리를 두 번 해제하려는 시도 자체가 불가능하다.10
  • 개발 생산성 향상: 이러한 자동화는 개발자가 복잡하고 오류 발생 가능성이 높은 메모리 생명주기 관리에서 벗어나, 애플리케이션의 핵심 비즈니스 로직에 더 집중할 수 있게 해준다.6 이는 단순히 보안 강화뿐만 아니라 개발 속도와 코드 품질 전반을 향상시키는 중요한 효과를 낳는다. C/C++ 개발자가 겪는 높은 인지 부하(cognitive load)를 상당 부분 제거해주는 것이다.

4.1.3 한계

물론 GC 방식에도 트레이드오프는 존재한다. GC가 실행되는 동안에는 프로그램의 실행이 잠시 멈출 수 있는데, 이를 ’Stop-the-World’라고 한다. 이러한 멈춤(pause) 시간은 예측이 불가능(non-deterministic)하므로, 수 마이크로초 단위의 엄격한 실시간성이 요구되는 임베디드 제어 시스템이나 고빈도 거래 시스템 등에는 부적합할 수 있다.42 또한, GC 자체의 실행과 메모리 추적을 위한 약간의 런타임 성능 오버헤드가 필연적으로 발생한다.

4.2 소유권(Ownership) 시스템 기반 언어: Rust

Rust는 가비지 컬렉터의 런타임 오버헤드 없이 C/C++ 수준의 성능을 유지하면서도 강력한 메모리 안전성을 보장하기 위해 ’소유권(Ownership)’이라는 독창적인 시스템을 도입했다.43

4.2.1 작동 원리

Rust의 메모리 관리는 컴파일러에 의해 강제되는 세 가지 핵심 규칙에 기반한다.43

  1. 하나의 값은 단 하나의 ‘소유자(owner)’ 변수를 가진다.
  2. 한 시점에 소유자는 오직 하나만 존재할 수 있다.
  3. 소유자가 스코프(scope)를 벗어나면, 그 값은 자동으로 해제(dropped)된다.

이 규칙들은 매우 엄격하게 적용된다. 예를 들어, 힙에 할당된 데이터를 가진 변수 s1을 다른 변수 s2에 할당하면(let s2 = s1;), 데이터가 복사되는 것이 아니라 데이터의 ’소유권’이 s2로 **이전(move)**된다. 소유권을 잃은 s1은 더 이상 유효하지 않으며, 이후 s1을 사용하려는 코드는 컴파일 오류를 발생시킨다.43

이 ‘소유권 이전’ 개념은 C/C++에서 두 포인터가 동일한 메모리를 가리킬 때 발생하는 ‘이중 해제’ 문제를 컴파일 시점에 원천적으로 차단한다. 오직 유일한 소유자만이 스코프를 벗어날 때 메모리를 해제할 책임이 있기 때문이다.44

또한, 다른 데이터의 값을 참조만 하고 싶을 때는 ’대여(Borrowing)’라는 개념을 사용한다. 대여는 특정 규칙(예: 하나의 가변 참조 또는 여러 개의 불변 참조만 허용)을 따라야 하며, 컴파일러는 ‘생명주기(Lifetimes)’ 분석을 통해 참조가 가리키는 데이터보다 오래 살아남아 댕글링 포인터가 되는 상황을 컴파일 단계에서 막아준다.46

4.2.2 컴파일 시점의 안전성 보장

Rust의 가장 혁신적인 점은 이 모든 메모리 안전성 검사가 런타임이 아닌 컴파일 시점에 이루어진다는 것이다.43 소유권, 대여, 생명주기 규칙을 위반하는 코드는 아예 컴파일 자체가 되지 않는다. 이는 C/C++처럼 런타임에 예기치 않게 발생하거나, Java처럼 GC에 의해 성능이 저하되는 방식이 아니다. 일단 Rust 코드가 성공적으로 컴파일되었다면, 메모리 안전성 오류가 없다는 것이 상당 부분 보장된다. 이러한 특성을 ’제로 코스트 추상화(Zero-Cost Abstraction)’라고 부르는데, 안전성을 확보하기 위한 추가적인 런타임 비용이 없다는 의미이다.43

4.2.3 보안상 이점

Rust의 소유권 시스템은 C/C++에서 발생하는 거의 모든 종류의 메모리 관련 취약점을 컴파일 단계에서 근절한다.44

  • 이중 해제 (Double Free): 소유권 이전(move)으로 인해 불가능하다.
  • 해제 후 사용 (Use-After-Free): 대여 규칙과 생명주기 분석을 통해 컴파일러가 댕글링 참조의 생성을 막는다.
  • 널 포인터 역참조: Rust는 널 포인터 개념 대신 Option<T> 타입을 사용하여 값이 없을 수 있는 상황을 명시적으로 처리하도록 강제한다.
  • 데이터 경쟁 (Data Race): 소유권과 대여 규칙은 여러 스레드가 동시에 데이터에 접근할 때 발생할 수 있는 데이터 경쟁 문제 또한 컴파일 시점에 방지한다.

이처럼 Rust는 C/C++의 성능과 제어 능력은 유지하면서도, 정교한 타입 시스템과 컴파일러의 도움을 받아 최고 수준의 메모리 안전성을 달성함으로써 시스템 프로그래밍 언어의 새로운 표준을 제시하고 있다.

5. 완화 전략 및 현대적 C++ 보안 강화 기법

C/C++로 작성된 방대한 규모의 레거시 코드베이스가 존재하는 현실 속에서, 모든 시스템을 하루아침에 메모리 안전 언어로 전환하는 것은 불가능하다. 따라서 기존 C/C++ 코드의 보안을 강화하고 잠재적 취약점을 완화하기 위한 현대적인 기법과 도구를 적극적으로 활용하는 것이 매우 중요하다.

도구 유형도구 이름주요 특징 및 탐지 항목장점단점
정적 분석 (SAST)Cppcheck정의되지 않은 동작, 메모리 누수, STL 오용, 위험한 코딩 패턴 탐지.오탐(False Positive)이 적고 사용이 간편함. 오픈 소스.탐지 범위가 다른 도구에 비해 제한적일 수 있음.
Clang-TidyLLVM/Clang 기반의 린터. 버그 유발 코드(bugprone), 보안 표준 위반(cert), 정적 분석 경고(clang-analyzer) 등 광범위한 체크 제공. 일부 자동 수정 기능.매우 상세하고 다양한 종류의 오류 탐지. C++ 최신 표준 지원.설정이 복잡하고 분석 시간이 길 수 있음.
동적 분석 (DAST)Valgrind (Memcheck)가상 CPU에서 프로그램 실행을 시뮬레이션하여 메모리 접근 감시. Invalid read/write, UAF, 메모리 누수 등 정밀 탐지.매우 정확하고 상세한 오류 보고. 소스 코드 수정 불필요.실행 속도가 매우 느림 (10-50배). 리눅스 환경에서 주로 사용.
AddressSanitizer (ASan)컴파일 시 코드 계측(instrumentation)을 통해 런타임 메모리 오류 탐지. 섀도우 메모리 사용. 버퍼 오버플로우, UAF 등 탐지.실행 속도 저하가 적음 (평균 2배). 다양한 플랫폼 지원.코드를 다시 컴파일해야 함.

5.1 Modern C++의 안전장치: 위험을 줄이려는 노력

C++ 표준은 시간이 지남에 따라 C-스타일 프로그래밍의 위험성을 줄이고 더 안전한 코드를 작성할 수 있도록 다양한 기능들을 도입해왔다.

5.1.1 스마트 포인터 (Smart Pointers)

Modern C++의 가장 중요한 보안 강화 기능은 ’스마트 포인터’이다. 스마트 포인터는 RAII(Resource Acquisition Is Initialization, 자원 획득은 초기화다)라는 C++의 핵심 원칙을 구현한 클래스 템플릿이다.47 RAII는 객체가 생성될 때 자원(메모리 등)을 획득하고, 객체가 소멸될 때(스코프를 벗어날 때) 자원을 자동으로 해제하는 패턴을 말한다. 이를 통해 원시 포인터(raw pointer) 사용 시 발생하는 많은 문제를 해결할 수 있다.

  • std::unique_ptr: 특정 메모리 블록에 대한 ’배타적 소유권’을 나타낸다. unique_ptr는 복사될 수 없으며, 오직 이동(move)만 가능하다. unique_ptr 객체가 소멸되면,它가 소유하던 메모리도 자동으로 해제된다. 이는 메모리 누수와 이중 해제 문제를 효과적으로 방지한다.47
  • std::shared_ptr: ’참조 카운팅(reference counting)’을 통해 여러 포인터가 하나의 메모리 블록을 ’공유 소유’할 수 있게 한다. 참조 카운터는 해당 메모리를 가리키는 shared_ptr의 개수를 추적하며, 이 카운터가 0이 될 때 메모리가 자동으로 해제된다. 이를 통해 복잡한 소유권 관계에서도 안전하게 메모리를 관리할 수 있다.47
  • std::weak_ptr: shared_ptr가 서로를 가리켜 발생하는 ‘순환 참조’ 문제를 해결하기 위해 사용된다. weak_ptr는 객체를 참조하지만 소유권에는 관여하지 않아 참조 카운터를 증가시키지 않는다. 따라서 순환 참조 고리를 끊어 메모리 누수를 방지할 수 있다.13

스마트 포인터를 사용하면 newdelete를 직접 호출할 필요가 거의 없어지므로, 수동 메모리 관리에서 비롯되는 대부분의 실수를 예방할 수 있다.50

5.1.2 표준 라이브러리 컨테이너 및 문자열

C-스타일의 원시 배열(int arr;)과 문자열(char* str)은 크기 정보를 포함하지 않고 경계 검사를 제공하지 않아 버퍼 오버플로우의 주된 원인이 된다. Modern C++에서는 이들을 대체할 안전한 표준 라이브러리 구성 요소를 사용하는 것이 강력히 권장된다.

  • std::vector, std::array: 이 컨테이너들은 내부적으로 자신의 크기를 관리하며, size() 멤버 함수를 통해 언제든지 크기를 확인할 수 있다. 특히 .at() 멤버 함수를 사용하면 배열의 경계를 벗어나는 접근 시 예외(std::out_of_range)를 발생시켜 런타임에 오류를 안전하게 처리할 수 있다.50 또한, 컨테이너 객체가 소멸될 때 저장된 요소들의 메모리도 자동으로 해제된다.55
  • std::string: C-스타일 문자열(char*)을 대체하는 클래스이다. 문자열의 길이를 동적으로 관리하며, 문자열 연결이나 복사 시 필요한 메모리를 자동으로 할당하고 해제한다. strcpy, strcat과 같은 위험한 함수를 사용할 필요가 없어 버퍼 오버플로우의 위험을 크게 줄여준다.9

5.2 정적 및 동적 분석 도구의 활용

아무리 주의를 기울여도 인간 프로그래머는 실수를 할 수 있다. 따라서 자동화된 도구를 활용하여 코드에 숨어있는 잠재적 취약점을 찾아내는 것이 필수적이다.

5.2.1 정적 분석 (SAST - Static Application Security Testing)

정적 분석 도구는 소스 코드를 실행하지 않고 코드 자체의 구조와 패턴을 분석하여 잠재적인 버그와 보안 취약점을 찾아낸다.

  • Cppcheck: 널리 사용되는 오픈 소스 정적 분석 도구로, 정의되지 않은 동작, 메모리 누수, 초기화되지 않은 변수 사용, STL의 잘못된 사용 등 다양한 종류의 버그를 탐지하는 데 중점을 둔다.58 Cppcheck의 가장 큰 장점은 오탐(false positive), 즉 실제 버그가 아닌데 버그라고 보고하는 경우를 최소화하려는 설계 목표에 있다.59
  • Clang-Tidy: LLVM/Clang 컴파일러 인프라를 기반으로 하는 강력한 린터(linter) 도구이다.60 bugprone(버그 유발 가능성이 높은 코드), cert(CERT 보안 코딩 표준 위반), clang-analyzer(더 깊이 있는 정적 분석) 등 수백 개의 체크 모듈을 제공하여 매우 광범위한 문제를 진단할 수 있다.60 또한, 많은 경우 발견된 문제를 자동으로 수정해주는 기능도 제공하여 코드 품질 개선에 매우 유용하다.62

5.2.2 동적 분석 (DAST - Dynamic Application Security Testing)

동적 분석 도구는 프로그램을 실제로 실행하면서 발생하는 런타임 오류를 탐지한다. 특히 정적 분석만으로는 찾기 어려운 메모리 관련 오류를 발견하는 데 매우 효과적이다.

  • Valgrind (Memcheck): 리눅스 환경에서 널리 사용되는 동적 분석 프레임워크로, 그중 Memcheck 도구는 메모리 오류 탐지에 특화되어 있다.64 Valgrind는 프로그램을 가상의 CPU 위에서 실행하며 모든 메모리 읽기/쓰기 작업을 감시한다. 이를 통해 할당된 메모리 경계를 벗어나는 접근(버퍼 오버플로우), 해제된 메모리 사용(UAF), 초기화되지 않은 변수 사용, 메모리 누수 등 거의 모든 종류의 메모리 오류를 매우 정밀하게 탐지해낸다.46 다만, 시뮬레이션 방식으로 작동하기 때문에 프로그램 실행 속도가 10배에서 50배까지 느려질 수 있다는 단점이 있다.66
  • AddressSanitizer (ASan): Google에서 개발한 동적 분석 도구로, Valgrind보다 훨씬 빠른 속도를 자랑한다.66 ASan은 컴파일 시점에 코드에 추가적인 검사 로직(instrumentation)을 삽입하는 방식으로 작동한다. 프로그램의 메모리 공간과 1:8로 매핑되는 ’섀도우 메모리(Shadow Memory)’라는 별도의 공간을 사용하여 모든 메모리 바이트의 유효성(접근 가능, 해제됨, 접근 불가 등)을 추적한다.66 프로그램이 메모리에 접근할 때마다, 컴파일러가 삽입한 코드가 섀도우 메모리를 먼저 확인하여 해당 접근이 유효한지 검사한다. 만약 유효하지 않은 접근(예: 버퍼 오버플로우, UAF)이 발생하면 즉시 프로그램을 중단시키고 상세한 오류 보고서를 출력한다.66 ASan의 성능 저하는 평균 2배 정도로 Valgrind에 비해 매우 낮아, 개발 및 테스트 단계에서 상시 활성화하여 사용하기에 적합하다.67

5.3 보안 코딩 표준 준수

안전한 C/C++ 코드를 작성하기 위해 오랜 기간 축적된 모범 사례들을 집대성한 것이 보안 코딩 표준이다. 이러한 표준을 준수하고, 정적 분석 도구를 통해 준수 여부를 검증하는 것은 코드의 신뢰성을 높이는 중요한 과정이다.

  • CERT C/C++: 카네기 멜런 대학의 소프트웨어 공학 연구소(SEI)에서 제정한 보안 코딩 표준으로, 안전하고 신뢰성 있으며 보안이 강화된 시스템을 개발하는 것을 목표로 한다.72 선언 및 초기화(DCL), 정수(INT), 메모리 관리(MEM), 입출력(FIO) 등 다양한 영역에 걸쳐 구체적인 규칙과 권장 사항을 제공하며, 위반 시 발생할 수 있는 위험의 심각도, 발생 가능성, 수정 비용을 평가하여 우선순위를 제시한다.20
  • MISRA C++: 본래 자동차 산업의 기능 안전을 위해 만들어졌으나, 현재는 항공, 국방, 의료기기 등 안전이 최우선으로 요구되는 다양한 임베디드 시스템 분야에서 널리 사용되는 코딩 가이드라인이다.76 MISRA C++는 언어 표준에서 정의되지 않은 동작(undefined behavior)이나 구현에 따라 달라지는 동작(implementation-defined behavior)의 사용을 엄격히 금지하고, 코드의 모호성을 제거하여 예측 가능하고 신뢰할 수 있는 소프트웨어를 작성하는 데 중점을 둔다.78

이러한 현대적 C++ 기능, 분석 도구, 코딩 표준을 종합적으로 활용함으로써, 프로그래머는 C/C++의 태생적 위험성을 상당 부분 완화하고 훨씬 더 안전하고 견고한 소프트웨어를 개발할 수 있다.

6. 결론: C/C++의 미래와 보안 패러다임의 변화

6.1 C/C++의 불가피성과 책임

C와 C++가 수많은 보안 이슈의 근원이 되어왔음에도 불구하고, 이 언어들이 가까운 미래에 사라질 것이라고 예측하기는 어렵다. 운영체제 커널, 임베디드 시스템, 고성능 컴퓨팅(HPC), 실시간 그래픽 렌더링, 게임 엔진과 같이 하드웨어에 대한 직접적인 제어와 압도적인 성능이 필수적인 특정 도메인에서 C/C++는 여전히 대체 불가능한 위치를 점하고 있기 때문이다.1 전 세계에 구축된 수십억 줄의 C/C++ 레거시 코드베이스는 이러한 현실을 더욱 공고히 한다.1

따라서 C/C++를 둘러싼 현재의 논의는 ’완전한 폐기’가 아닌 ’책임감 있는 사용’으로 귀결되어야 한다. 이는 C/C++ 개발자에게 더 높은 수준의 전문성과 책임 의식을 요구한다. 단순히 기능 구현에 그치는 것이 아니라, 언어의 태생적 위험성을 명확히 인지하고, 스마트 포인터와 같은 현대적 C++ 기능을 적극적으로 활용하며, 정적/동적 분석 도구를 개발 프로세스에 필수적으로 통합하고, CERT나 MISRA와 같은 보안 코딩 표준을 철저히 준수하는 등, 코드의 안전성을 확보하기 위한 다층적인 노력을 체화해야 한다.6 이제 C/C++ 개발자는 필연적으로 보안 전문가의 소양을 갖추어야만 하는 시대적 요구에 직면해 있다.

6.2 ’안전 설계(Secure by Design)’로의 전환

C/C++의 메모리 안전성 문제는 지난 수십 년간의 소프트웨어 개발 패러다임에 근본적인 질문을 던진다. 전통적인 ‘개발 후 취약점 탐지 및 수정(find-and-patch)’ 모델은 C/C++가 양산하는 수많은 메모리 취약점 앞에서 한계를 드러냈다. 전체 취약점의 70%가 동일한 근본 원인에서 비롯된다는 사실은, 개별 버그를 수정하는 방식으로는 이 전쟁에서 결코 이길 수 없음을 보여준다.

따라서 패러다임은 ‘설계 단계부터 보안을 내재화하는(Secure by Design)’ 방향으로 전환되어야 한다. 이는 단순히 안전한 라이브러리 함수를 선택하는 수준을 넘어, 프로젝트의 요구사항을 분석하는 가장 초기 단계에서부터 ’메모리 안전성’을 핵심적인 품질 지표이자 기능적 요구사항으로 고려해야 함을 의미한다. 새로운 프로젝트를 시작할 때, C/C++가 제공하는 극한의 성능이 반드시 필요한지, 아니면 Rust, Go, Java와 같은 메모리 안전 언어가 제공하는 개발 생산성과 내재된 안전성이 더 큰 가치를 가지는지를 신중하게 평가하는 과정이 표준 프로세스로 자리 잡아야 한다.

6.3 미래 전망: 진화와 공존

미래의 소프트웨어 생태계는 단일 언어가 지배하는 형태가 아닌, 각자의 강점을 가진 언어들이 공존하는 하이브리드 형태가 될 가능성이 높다. C++ 언어 자체는 표준 위원회를 중심으로 메모리 안전성을 강화하는 방향으로 꾸준히 진화할 것이다.8 동시에, Rust와 같은 차세대 시스템 프로그래밍 언어는 높은 안전성과 성능을 바탕으로 새로운 시스템 개발의 표준으로 점차 자리매김할 것이다.1

결과적으로, 고성능이 요구되는 핵심 엔진이나 하드웨어 제어 계층은 보안 원칙을 철저히 준수하여 작성된 Modern C++ 코드로 구현되고, 애플리케이션 로직이나 네트워크 서비스와 같은 상위 계층은 Rust, Go, C# 등의 메모리 안전 언어로 개발되는 아키텍처가 보편화될 수 있다.

결론적으로, C/C++의 보안 이슈는 이 언어들의 종말을 고하는 것이 아니라, 소프트웨어 개발의 새로운 시대를 여는 촉매제 역할을 하고 있다. 이 시대를 살아가는 개발자들은 과거의 관행에 안주하지 않고, 언어의 위험성을 깊이 이해하며, 가용한 모든 도구와 지식을 동원하여 안전하고 신뢰할 수 있는 소프트웨어를 구축해야 할 중대한 책임을 안고 있다. C/C++의 미래는 그것을 사용하는 우리 손에 달려 있다.

7. Works cited

  1. 백악관, 개발자들에게 C와 C++를 피하고 ‘메모리 안전’ 언어 사용 - GeekNews, accessed October 28, 2025, https://news.hada.io/topic?id=13581
  2. Stop Coding In C and C++, Feds Say - IT Jungle, accessed October 28, 2025, https://www.itjungle.com/2024/12/02/stop-coding-in-c-and-c-feds-say/
  3. NSA Cybersecurity Information Sheet remarks on C and C++. : r/cpp - Reddit, accessed October 28, 2025, https://www.reddit.com/r/cpp/comments/ys48kb/nsa_cybersecurity_information_sheet_remarks_on_c/
  4. 오픈소스 과반 ’메모리 손상 언어’로 취약점 위험 - 애플경제, accessed October 28, 2025, https://www.apple-economy.com/news/articleView.html?idxno=73726
  5. C(프로그래밍 언어)/포인터 - 나무위키, accessed October 28, 2025, https://namu.wiki/w/C(%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%20%EC%96%B8%EC%96%B4)/%ED%8F%AC%EC%9D%B8%ED%84%B0
  6. Transitioning to memory-safe languages: Challenges and considerations - Help Net Security, accessed October 28, 2025, https://www.helpnetsecurity.com/2024/03/11/omkhar-arasaratnam-openssf-memory-safe-programming-languages/
  7. 왜 C언어 포인터는 이해하기 어려울까? - 인프런 | 스토리, accessed October 28, 2025, https://www.inflearn.com/pages/dc-pointer-202310
  8. In Defense of C/C++ (Part 1) - Cybersecurity Magazine, accessed October 28, 2025, https://cybersecurity-magazine.com/in-defense-of-c-c-part-1/
  9. Why are programs written in C and C++ so frequently vulnerable to overflow attacks?, accessed October 28, 2025, https://security.stackexchange.com/questions/115507/why-are-programs-written-in-c-and-c-so-frequently-vulnerable-to-overflow-attac
  10. Efficient Memory Management: Exploring Garbage Collection in C …, accessed October 28, 2025, https://medium.com/@ghosalarjun/efficient-memory-management-exploring-garbage-collection-in-c-c-and-the-purge-project-d7f87db9bb95
  11. How do different programming languages manage memory? · community · Discussion #137317 - GitHub, accessed October 28, 2025, https://github.com/orgs/community/discussions/137317
    1. c 와 c++ 에서의 취약점 발생 유형, accessed October 28, 2025, https://oulth.tistory.com/51
  12. 프로그래밍에서의 메모리 누수: 원인, 탐지 및 예방 이해 - IN-COM …, accessed October 28, 2025, https://www.in-com.com/ko/blog/understanding-memory-leaks-in-programming-causes-detection-and-prevention/
  13. Top 5 C++ security risks | Snyk, accessed October 28, 2025, https://snyk.io/blog/top-5-c-security-risks/
  14. 강의 내용 정리(Section5 / 버그 유형) - 정체불명의 모모 - 티스토리, accessed October 28, 2025, https://uncertainty-momo.tistory.com/m/96
  15. 버퍼 오버플로 - 위키백과, 우리 모두의 백과사전, accessed October 28, 2025, https://ko.wikipedia.org/wiki/%EB%B2%84%ED%8D%BC_%EC%98%A4%EB%B2%84%ED%94%8C%EB%A1%9C
  16. 버퍼오버플로우(Buffer Overflow) 해킹기법이란?? - 캐스피의 블로그 - 티스토리, accessed October 28, 2025, https://kaspyx.tistory.com/2
  17. 버퍼 오버플로우 공격(Buffer Overflow Attack) - Jun_ : Pwn - 티스토리, accessed October 28, 2025, https://she11.tistory.com/123
  18. [010] BoF(Buffer overflow)에 대해 알아보자!, accessed October 28, 2025, https://movefun-tech.tistory.com/14
  19. C 프로그래밍의 보안 이슈와 포인터 남용, accessed October 28, 2025, https://thinkpro.tistory.com/87
  20. Use-After-Free vulnerability | CQR, accessed October 28, 2025, https://cqr.company/web-vulnerabilities/use-after-free-vulnerability/
  21. 메모리 보안 - 위키백과, 우리 모두의 백과사전, accessed October 28, 2025, https://ko.wikipedia.org/wiki/%EB%A9%94%EB%AA%A8%EB%A6%AC_%EB%B3%B4%EC%95%88
  22. Using freed memory | OWASP Foundation, accessed October 28, 2025, https://owasp.org/www-community/vulnerabilities/Using_freed_memory
  23. Uncovering Use-After-Free Conditions In Compiled Code - UF CISE, accessed October 28, 2025, https://cise.ufl.edu/~traynor/papers/uaf15.pdf
  24. What is a Use After Free Vulnerability? - Huntress, accessed October 28, 2025, https://www.huntress.com/cybersecurity-101/topic/what-is-use-after-free
  25. What is Use After Free Vulnerability? - Automox, accessed October 28, 2025, https://www.automox.com/blog/vulnerability-definition-use-after-free
  26. [ProjectH4C] HackCTF (Basic_FSB) - 티스토리, accessed October 28, 2025, https://155734.tistory.com/54
  27. 포맷 스트링 버그(Format String Bug, FSB) 취약점 :: Note, accessed October 28, 2025, https://joodaeng.tistory.com/entry/%ED%8F%AC%EB%A7%B7-%EC%8A%A4%ED%8A%B8%EB%A7%81-%EB%B2%84%EA%B7%B8Format-String-Bug-FSB-%EC%B7%A8%EC%95%BD%EC%A0%90
  28. 포맷 스트링 버그(Format string bug) 취약점이란? - 캐스피의 블로그 - 티스토리, accessed October 28, 2025, https://kaspyx.tistory.com/74
  29. FSB(Format String Bug)란 무엇인가? - 1 - 끄적끄적 - 티스토리, accessed October 28, 2025, https://07vh.tistory.com/entry/FSBFormat-String-Bug%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80
  30. 포맷 스트링 버그 - 위키백과, 우리 모두의 백과사전, accessed October 28, 2025, https://ko.wikipedia.org/wiki/%ED%8F%AC%EB%A7%B7_%EC%8A%A4%ED%8A%B8%EB%A7%81_%EB%B2%84%EA%B7%B8
  31. C++ 프로그램에서 memory leak 발생한 사건 | 소멸자 사용하기, accessed October 28, 2025, https://venypark.tistory.com/168
  32. [C++] 메모리 관리 (2) - 메모리 문제 유형과 해결 방법 - 별준, accessed October 28, 2025, https://junstar92.tistory.com/307
  33. Use after free vulnerability | Tutorial & Examples - Snyk Learn, accessed October 28, 2025, https://learn.snyk.io/lesson/use-after-free/
    1. C언어 메모리 관리의 어려움 (2/2) - C언어 메모리 문제 유형들, accessed October 28, 2025, https://www.kernelpanic.kr/33
  34. Software Memory Safety, accessed October 28, 2025, https://media.defense.gov/2022/Nov/10/2003112742/-1/-1/0/CSI_SOFTWARE_MEMORY_SAFETY.PDF
  35. Memory Management in Python - GeeksforGeeks, accessed October 28, 2025, https://www.geeksforgeeks.org/python/memory-management-in-python/
  36. Python Garbage Collection: What It Is and How It Works - Stackify, accessed October 28, 2025, https://stackify.com/python-garbage-collection/
  37. Understanding Python’s Memory Management: Reference Counting …, accessed October 28, 2025, https://dev.to/nkpydev/understanding-pythons-memory-management-reference-counting-garbage-collection-and-optimization-4gn7
  38. Python does reference counting. The downside is that you can leak memory if you, accessed October 28, 2025, https://news.ycombinator.com/item?id=7488880
  39. How does garbage collection compare to reference counting? [duplicate], accessed October 28, 2025, https://softwareengineering.stackexchange.com/questions/285333/how-does-garbage-collection-compare-to-reference-counting
  40. Manual memory management - Wikipedia, accessed October 28, 2025, https://en.wikipedia.org/wiki/Manual_memory_management
  41. What Is Ownership in Rust? A Deep Dive into Safe and Fast Memory Management - Medium, accessed October 28, 2025, https://medium.com/@estheraladioche569/what-is-ownership-in-rust-a-deep-dive-into-safe-and-fast-memory-management-459a418cd326
  42. Understanding Rust Ownership: A Complete Guide to Memory Safety - DEV Community, accessed October 28, 2025, https://dev.to/ajtech0001/understanding-rust-ownership-a-complete-guide-to-memory-safety-258o
  43. What is Ownership? - The Rust Programming Language, accessed October 28, 2025, https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html
  44. 백악관: 미래 소프트웨어는 메모리 안전해야 함 : r/cpp - Reddit, accessed October 28, 2025, https://www.reddit.com/r/cpp/comments/1b0pnn0/white_house_future_software_should_be_memory_safe/?tl=ko
  45. Smart pointers (Modern C++) | Microsoft Learn, accessed October 28, 2025, https://learn.microsoft.com/en-us/cpp/cpp/smart-pointers-modern-cpp?view=msvc-170
  46. Should I prioritize smart pointers over raw ones? : r/cpp_questions - Reddit, accessed October 28, 2025, https://www.reddit.com/r/cpp_questions/comments/15ugd91/should_i_prioritize_smart_pointers_over_raw_ones/
  47. How do you decide when to use smart pointers vs raw pointers in modern C++? - Reddit, accessed October 28, 2025, https://www.reddit.com/r/cpp/comments/1ibxuyn/how_do_you_decide_when_to_use_smart_pointers_vs/
  48. What’s so “dangerous” about C/C++? : r/learnprogramming - Reddit, accessed October 28, 2025, https://www.reddit.com/r/learnprogramming/comments/1bs86ba/whats_so_dangerous_about_cc/
  49. When to Use Raw Pointers - Hitchcock Codes, accessed October 28, 2025, https://hitchcock.codes/blog/when-to-use-raw-pointers
  50. Smart pointer vs owning raw pointer - c++ - Stack Overflow, accessed October 28, 2025, https://stackoverflow.com/questions/30712092/smart-pointer-vs-owning-raw-pointer
  51. When should I use raw pointers over smart pointers? - Stack Overflow, accessed October 28, 2025, https://stackoverflow.com/questions/6675651/when-should-i-use-raw-pointers-over-smart-pointers
  52. cpp_016: Comparing C-Style Arrays, std::array, and std::vector in …, accessed October 28, 2025, https://medium.com/@staytechrich/cpp-016-comparing-c-style-arrays-std-array-and-std-vector-in-c-5bf0fed18359
  53. std::vector vs std::array vs C-style array for matrix implementation : r/cpp_questions - Reddit, accessed October 28, 2025, https://www.reddit.com/r/cpp_questions/comments/17wjjzj/stdvector_vs_stdarray_vs_cstyle_array_for_matrix/
  54. Performance of C-style arrays vs C++ std::vector - MatecDev, accessed October 28, 2025, https://www.matecdev.com/posts/cpp-vector-array-performance.html
  55. std::vector versus std::array in C++ - Stack Overflow, accessed October 28, 2025, https://stackoverflow.com/questions/4424579/stdvector-versus-stdarray-in-c
  56. Cppcheck manual - SourceForge, accessed October 28, 2025, https://cppcheck.sourceforge.io/manual.html
  57. Cppcheck - A tool for static C/C++ code analysis, accessed October 28, 2025, https://cppcheck.sourceforge.io/
  58. Clang-Tidy — Extra Clang Tools 22.0.0git documentation - LLVM, accessed October 28, 2025, https://clang.llvm.org/extra/clang-tidy/
  59. Clang-Tidy Rules and Checks, accessed October 28, 2025, https://android.googlesource.com/platform/build/soong/+/48d9ec056/docs/tidy.md
  60. Clang Tidy - Defensive programming and debugging, accessed October 28, 2025, https://gjbex.github.io/Defensive_programming_and_debugging/CodeValidation/StaticCodeAnalyzers/clang_tidy/
  61. Using Clang-Tidy in Visual Studio - Microsoft Learn, accessed October 28, 2025, https://learn.microsoft.com/en-us/cpp/code-quality/clang-tidy?view=msvc-170
  62. CS107 Valgrind Memcheck - Stanford University, accessed October 28, 2025, http://web.stanford.edu/class/archive/cs/cs107/cs107.1262/resources/valgrind
  63. Valgrind User Manual, accessed October 28, 2025, https://valgrind.org/docs/manual/manual.html
  64. Understanding AddressSanitizer: Better memory safety for your code …, accessed October 28, 2025, https://blog.trailofbits.com/2024/05/16/understanding-addresssanitizer-better-memory-safety-for-your-code/
  65. AddressSanitizer · google/sanitizers Wiki - GitHub, accessed October 28, 2025, https://github.com/google/sanitizers/wiki/addresssanitizer
  66. AddressSanitization and Why You Should Use It - Sean Deaton, accessed October 28, 2025, https://www.seandeaton.com/addresssanitization-and-why-you-should-use-it/
  67. AddressSanitizer - Lei Mao’s Log Book, accessed October 28, 2025, https://leimao.github.io/blog/AddressSanitizer/
  68. AddressSanitizer shadow bytes | Microsoft Learn, accessed October 28, 2025, https://learn.microsoft.com/en-us/cpp/sanitizers/asan-shadow-bytes?view=msvc-170
  69. AddressSanitizer: A Fast Address Sanity Checker - Google Research, accessed October 28, 2025, https://research.google.com/pubs/archive/37752.pdf
  70. CERT Coding Standard - QA Systems, accessed October 28, 2025, https://www.qa-systems.com/solutions/cert/
  71. What Is CERT C? - MATLAB & Simulink - MathWorks, accessed October 28, 2025, https://www.mathworks.com/discovery/cert-c.html
  72. What Is CERT C++? Definitions and Examples - Parasoft, accessed October 28, 2025, https://www.parasoft.com/blog/theres-no-good-reason-to-ignore-cert-c/
  73. CERT C Coding Standard: Secure Software Development Guide - LDRA, accessed October 28, 2025, https://ldra.com/sei-cert/
  74. What Is MISRA C? - MATLAB & Simulink - MathWorks, accessed October 28, 2025, https://www.mathworks.com/discovery/misra-c.html
  75. MISRA C - Wikipedia, accessed October 28, 2025, https://en.wikipedia.org/wiki/MISRA_C
  76. MISRA C++ 2023 Guide: Everything You Need to Know - Parasoft, accessed October 28, 2025, https://www.parasoft.com/blog/misra-cpp-2023-guide/
  77. MISRA C and MISRA C++ - more than just safety - Qt, accessed October 28, 2025, https://www.qt.io/quality-assurance/blog/misra-more-than-safety
  78. MISRA-C/C++:2023 – Elevating safety standards for C/C++ developers - IAR, accessed October 28, 2025, https://www.iar.com/blog/misra-c2023-elevating-safety-standards-for-c-developers
  79. MISRA C++ 2023 Coding Standard Guidelines - Sonar, accessed October 28, 2025, https://www.sonarsource.com/knowledge/languages/cpp/misra-cpp-2023/